package eval;

import config.Config;
import earlyDetection.Automata;
import earlyDetection.PolicyChecker;
import earlyDetection.PolicyCheckerNaive;
import earlyDetection.PolicyCheckerNaiveSleep;
import earlyDetection.pg.PGEdge;
import network.Device;
import network.Network;
import network.Port;
import networkLoader.FBNetwork;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedGraph;
import verifier.JiffyVerifier;
import verifier.Ports.Ports;
import verifier.util.Changes;
import verifier.util.PPM;

import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

public class PolicyCheckEval {
    static long ip = (111 << 24) + (1 << 16);
    public static void main(String[] args) {

        Network network = FBNetwork.getErrorNetwork(ip);
//        pgCheck(network);
        naiveCheck(network);
//        naiveCheckIncre(network);
    }

    public static void pgCheck(Network network) {
        Graph<Device, PGEdge> pg = buildPG(network);

        JiffyVerifier verifier = new JiffyVerifier(network);

        int hs = verifier.bddEngine.encodeIpv4(BigInteger.valueOf(ip), 16);
        PolicyChecker policyChecker = new PolicyChecker(network, pg, verifier.bddEngine, hs);
        Config.startAt = System.nanoTime();
        if (false) {
            Device src = network.getDevice("rsw-0-0");
            Changes changes = verifier.insertMiniBatch(src.getInitialRules());
            verifier.update(changes);
            HashMap<Integer, Ports> ecToPorts = new HashMap<>();
            for (Map.Entry<Ports, Integer> entry : verifier.portsToPredicate.entrySet()) {
                ecToPorts.put(entry.getValue(), entry.getKey());
            }
            policyChecker.check(src, verifier.portsToPredicate.values(), ecToPorts);
        }
        List<Device> allDevices = new ArrayList<>(network.getAllDevices());
//        Collections.shuffle(allDevices);
        for (Device device : allDevices) {
            Changes changes = verifier.insertMiniBatch(device.getInitialRules());
            verifier.update(changes);
            Config.processedUpdates += device.getInitialRules().size();
            if (policyChecker.stop) {
                continue;
            }
            HashMap<Integer, Ports> ecToPorts = new HashMap<>();
            for (Map.Entry<Ports, Integer> entry : verifier.portsToPredicate.entrySet()) {
                ecToPorts.put(entry.getValue(), entry.getKey());
            }
            long s = System.nanoTime();
            policyChecker.check(device, verifier.portsToPredicate.values(), ecToPorts);
            System.out.println(System.nanoTime() - s);
        }
        System.out.println("$total: " + (System.nanoTime() - Config.startAt));
    }

    public static void naiveCheck(Network network) {
        ip = (110 << 24) + (48 << 16);
        Automata automata = createAutomata2(network);
        Graph<Device, PGEdge> pg = buildGraph(network);

        JiffyVerifier verifier = new JiffyVerifier(network);

        int hs = verifier.bddEngine.encodeIpv4(BigInteger.valueOf(ip), 16);
        PolicyCheckerNaive policyChecker = new PolicyCheckerNaive(network, pg, verifier.bddEngine, hs);
        Config.startAt = System.nanoTime();

        List<Device> allDevices = new ArrayList<>(network.getAllDevices());
//        Collections.shuffle(allDevices);
        for (Device device : allDevices) {
            Changes changes = verifier.insertMiniBatch(device.getInitialRules());
            verifier.update(changes);
            Config.processedUpdates += device.getInitialRules().size();
            if (policyChecker.stop) {
                continue;
            }
            HashMap<Integer, Ports> ecToPorts = new HashMap<>();
            for (Map.Entry<Ports, Integer> entry : verifier.portsToPredicate.entrySet()) {
                ecToPorts.put(entry.getValue(), entry.getKey());
            }
            long s = System.nanoTime();
            policyChecker.check(device, verifier.portsToPredicate.values(), ecToPorts, automata);
            System.out.println(System.nanoTime() - s);
        }
        System.out.println("$total: " + (System.nanoTime() - Config.startAt));
    }

    public static void naiveCheckIncre(Network network) {
        Automata automata = createAutomata(network);
        Graph<Device, PGEdge> pg = buildGraph(network);

        JiffyVerifier verifier = new JiffyVerifier(network);

        int hs = verifier.bddEngine.encodeIpv4(BigInteger.valueOf(ip), 16);
        PolicyCheckerNaiveSleep policyChecker = new PolicyCheckerNaiveSleep(network, pg, verifier.bddEngine, hs, automata);
        policyChecker.start();

        Config.startAt = System.nanoTime();
        List<Device> allDevices = new ArrayList<>(network.getAllDevices());
//        Collections.shuffle(allDevices);
        for (Device device : allDevices) {
            Changes changes = verifier.insertMiniBatch(device.getInitialRules());
            verifier.update(changes);
            Config.processedUpdates += device.getInitialRules().size();
            if (policyChecker.stop) {
                break;
            }
            HashMap<Integer, Ports> ecToPorts = new HashMap<>();
            for (Map.Entry<Ports, Integer> entry : verifier.portsToPredicate.entrySet()) {
                ecToPorts.put(entry.getValue(), entry.getKey());
            }
            policyChecker.interrupt(device, ecToPorts);
        }
        System.out.println("$total: " + (System.nanoTime() - Config.startAt));
    }

    static public Graph<Device, PGEdge> buildGraph(Network network) {
        Graph<Device, PGEdge> graph = new DefaultDirectedGraph<>(PGEdge.class);
        for (Device device : network.getAllDevices()) {
            graph.addVertex(device);
        }
        for (Device device : network.getAllDevices()) {
            for (Port port : device.getPorts()) {
                if (port.getPeerDevice() == null) {
                    continue;
                }
                graph.addEdge(device, port.getPeerDevice(), new PGEdge(device.getPort(device.getName() + ">" + port.getPeerDevice().getName())));
            }
        }
        return graph;
    }

    static public Graph<Device, PGEdge> buildPG(Network network) {

        Graph<Device, PGEdge> graph = new DefaultDirectedGraph<>(PGEdge.class);
        Device src = network.getDevice("rsw-0-0");
        Device dst = network.getDevice("rsw-111-0");
        graph.addVertex(src);
        graph.addVertex(dst);
        for (int i = 0; i < 4; i++) {
            graph.addVertex(network.getDevice("fsw-0-" + i));
            graph.addVertex(network.getDevice("fsw-111-" + i));
            for (int j = 0; j < 48; j++) {
                Device ssw = network.getDevice("ssw-" + i + "-" + j);
                graph.addVertex(ssw);
            }
        }
        for (int i = 0; i < 4; i++) {
            graph.addEdge(src, network.getDevice("fsw-0-" + i), new PGEdge(src.getPort(src.getName() + ">" + "fsw-0-" + i)));
            graph.addEdge(network.getDevice("fsw-111-" + i), dst, new PGEdge(network.getDevice("fsw-111-" + i).getPort("fsw-111-" + i + ">" + dst.getName())));
            for (int j = 0; j < 48; j++) {
                graph.addEdge(network.getDevice("fsw-0-" + i), network.getDevice("ssw-" + i + "-" + j), new PGEdge(network.getDevice("fsw-0-" + i).getPort("fsw-0-" + i + ">" + "ssw-" + i + "-" + j)));
                graph.addEdge(network.getDevice("ssw-" + i + "-" + j), network.getDevice("fsw-111-" + i), new PGEdge(network.getDevice("ssw-" + i + "-" + j).getPort("ssw-" + i + "-" + j + ">" + "ssw-" + i + "-" + j)));
            }
        }
        return graph;
    }

    public static Automata createAutomata(Network network) {
        HashSet<String> symbols = network.getAllDevices().stream().map(Device::getName).collect(Collectors.toCollection(HashSet::new));
        Automata automata = Automata.fromString("rsw-0-0,.3,rsw-111-0", symbols);
        return automata;
    }

    public static Automata createAutomata1(Network network) {
        HashSet<String> symbols = network.getAllDevices().stream().map(Device::getName).collect(Collectors.toCollection(HashSet::new));
        List<String> sarr = new ArrayList<>();
        for (int i = 0; i < 47; i++) {
            sarr.add("rsw-110-" + i);
        }
        String s = String.join("|", sarr);
        Automata automata = Automata.fromString("rsw-0-0,.2,fsw-110-0,"+s+",fsw-110-1|fsw-110-2|fsw-110-3,rsw-110-47", symbols);
        return automata;
    }

    public static Automata createAutomata2(Network network) {
        HashSet<String> symbols = network.getAllDevices().stream().map(Device::getName).collect(Collectors.toCollection(HashSet::new));
        List<String> sarr = new ArrayList<>();
        for (int i = 1; i < 48; i++) {
            sarr.add("rsw-0-" + i);
        }
        String s = String.join("|", sarr);
        Automata automata = Automata.fromString("rsw-0-0,fsw-0-0|fsw-0-1|fsw-0-2,"+ s + ",fsw-0-3,.2,rsw-110-47", symbols);
        return automata;
    }
}
